import functools
from Crypto.Cipher import DES

def hex_byte(hexStr):
    return bytes.fromhex(hexStr)

def byte2hex(bins):
    return ''.join(["{0:02X}".format(x) for x in bins])

def bytes_xor(filed1, filed2):
    return bytes([a ^ b for a, b in zip(filed1, filed2)])

def bin2hex(bins):
    return b'%0*X' % ((len(bins) + 3) // 4, int(bins, 2))

def bit_map(fields):
    raw_map = list(b'0' * 64)
    for field in fields:
        raw_map[field + 1] = b'1'
    raw_map = b''.join(raw_map)
    return bin2hex(raw_map)

def mac_xor(data):
    chunk_size = 8
    add = len(data)%chunk_size
    if add != 0:
        data += (chunk_size - add)*b'\0'
    data_size = len(data)
    xor_list = [data[i:i + 8] for i in range(0, data_size, chunk_size)]
    return functools.reduce(bytes_xor, xor_list)

def pos_mac(key, data):
    des = DES.new(key, DES.MODE_ECB)
    # xor result
    b_xor = mac_xor(data)
    # xor to hex get 8 byte
    xor8hex = byte2hex(b_xor)[:8].encode()
    # des encrypt
    encrypt_text = des.encrypt(xor8hex)

    text = bytes_xor(encrypt_text, byte2hex(b_xor)[8:].encode())
    encryp_text = des.encrypt(text)
    return byte2hex(encryp_text)[:8].encode()

FIELDS = {
    2: (1,'BCD'),
    3: 3,
    4: 6,
    11: 3,
    12: 3,
    13: 2,
    14: 2,
    15: 2,
    22: 2,
    23: 2,
    25: 1,
    26: 1,
    32: (1,'BCD'),
    35:(1, 'BCD'),
    36: (2,'BCD'),
    37: 12,
    38: 6,
    39: 2,
    41: 8,
    42: 15,
    44: (1, 'ASCII'),
    48: (2, 'BCD'),
    49: 3,
    52: 8,
    53: 8,
    54: (2,'ASCII'),
    55: (2,'BCD'),
    58: (2, 'BCD'),
    60: (2, 'BCD'),
    61: (2, 'BCD'),
    63: (2, 'ASCII'),
    64: 8
}

def parse_8583_content(content):
    mti, content = content[:2], content[2:]
    bit_map, content = content[:8], content[8:]
    bit_map =  "".join([format(i, '#010b')[2:] for i in bit_map])
    bit_map = [index + 1 for index, i in enumerate(bit_map) if int(i) == 1]
    for field in bit_map:
        length = FIELDS[field]
        if isinstance(length, tuple):
            length, is_bcd = length
            len_field = int(byte2hex(content[:length]))
            if is_bcd == 'BCD':
                len_field = round(len_field/2)
            length += len_field
            filed_content, content = content[:length], content[length:]
        else:
            filed_content, content = content[:length], content[length:]
        yield field, filed_content

def content_8583(content_dict):
    return b"".join([v for k, v in content_dict.items()])

def check_mac(key, obj, mac):
    text = content_8583(obj)
    _mac = pos_mac(key, text)
    if _mac == mac:
        return True
    else:
        return False